//=============================================================================
//
//		Copyright (C) 1995, 1996 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscBorderView.M
//
//		View class for the row/column borders on a MiscTableView.
//		Supports resizing, dragging.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscBorderView.M,v 1.9 96/02/12 00:04:25 sunshine Exp $
// $Log:		MiscBorderView.M,v $
//	Revision 1.9  96/02/12	00:04:25  sunshine
//	no comment.
//	
//	Revision 1.8  96/01/17	05:17:09  sunshine
//	Fixed crasher.	Was allocating drag-cache window (an alloc once free never
//	object) from [self zone].  When the zone was destroyed the window pointer
//	(in a static variable) ceased to point to valid memory.
//	
//	Revision 1.7  96/01/16	19:51:25  zarnuk
//	Eliminated a lot of unnecessary drawing while dragging.
//-----------------------------------------------------------------------------
#import "MiscBorderView.h"
#import "MiscBorderCell.h"
#import "MiscHighlightTracker.h"
#import "MiscListTracker.h"
#import "MiscRadioTracker.h"
#import "MiscTableBorder.h"
#import "MiscTableView.h"
#import "MiscTableWell.h"
#import <misckit/MiscTableScroll.h>
#import <misckit/MiscTableTypes.h>

#import <new.h>

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/ClipView.h>
#import <appkit/Font.h>
#import <appkit/NXCursor.h>
#import <appkit/NXImage.h>
#import <appkit/Text.h>
#import <appkit/Window.h>
#import <appkit/graphics.h>
#import <appkit/timer.h>
#import <objc/NXBundle.h>
#import <dpsclient/dpsNeXT.h>
#import <dpsclient/wraps.h>
}

extern "C" {
#import <assert.h>
#import <float.h>
#import <limits.h>
#import <math.h>
#import <string.h>		// memset().
#import <stdio.h>		// FILENAME_MAX
}

#define MISC_RESIZE_EPSILON		5
#define MISC_FRAME_HEIGHT		18
#define MISC_FRAME_WIDTH		46

static NXCursor* horzCursor = 0;
static NXCursor* vertCursor = 0;

static inline double dmin( double a, double b ) { return (a < b ? a : b); }
static inline double dmax( double a, double b ) { return (a > b ? a : b); }


//----------------------------------------------------------------------------
// startTimer
//----------------------------------------------------------------------------
static inline void startTimer( NXTrackingTimer*& timer )
	{
	if (timer == 0)
		timer = NXBeginTimer( 0, 0.1, 0.1 );
	}


//----------------------------------------------------------------------------
// stopTimer
//----------------------------------------------------------------------------
static inline void stopTimer( NXTrackingTimer*& timer )
	{
	if (timer)
		{
		NXEndTimer( timer );
		timer = 0;
		}
	}



//=============================================================================
// IMPLEMENTATION
//=============================================================================
@implementation MiscBorderView

//-----------------------------------------------------------------------------
// + cursorFromImageName:
//		NOTE: Cannot use [self zone] in a class (factory) method
//		like this one, hence no allocFromZone:.
//-----------------------------------------------------------------------------
+ (NXCursor*) cursorFromImageName:(char const*)name
	{
	NXImage* image = [NXImage findImageNamed:name];
	if (image == 0)
		{
		char path[ FILENAME_MAX + 1 ];
		[[NXBundle bundleForClass:self]
				getPath:path forResource:name ofType:"tiff"];
		image = [[NXImage alloc] initFromFile:path];
		[image setName:name];
		}
	NXPoint const hot = { 8, 8 };
	NXCursor* cursor = [[NXCursor alloc] initFromImage: image];
	[cursor setHotSpot:&hot];
	return cursor;
	}


//-----------------------------------------------------------------------------
// + initialize
//-----------------------------------------------------------------------------
+ initialize
	{
	if (horzCursor == 0)
		{
		horzCursor = [self cursorFromImageName:"MiscHorzResizeCursor"];
		vertCursor = [self cursorFromImageName:"MiscVertResizeCursor"];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// TYPE VARIATIONS
//-----------------------------------------------------------------------------

- (NXCoord) frameHeight
		{ return (isHorz ? MISC_FRAME_HEIGHT : MISC_FRAME_WIDTH); }
- (NXCursor*) cursor
		{ return isHorz ? horzCursor : vertCursor; }
- (MiscBorderType) borderType
		{ return isHorz ? MISC_COL_BORDER : MISC_ROW_BORDER; }
- (MiscBorderType) otherBorderType
		{ return isHorz ? MISC_ROW_BORDER : MISC_COL_BORDER; }
- (MiscTableBorder*) otherBorder
		{ return [scroll border: [self otherBorderType]]; }
- (void) clearOtherBorder { [self otherBorder]->selectionSet().empty(); }
- cellAt: (MiscCoord_P) row : (MiscCoord_P) col
	{ return isHorz ? [scroll cellAt:row:col] : [scroll cellAt:col:row]; }

- (NXCoord) sizeWidth: (NXSize const*) size
		{ return isHorz ? size->width : size->height; }
- (NXCoord) sizeHeight: (NXSize const*) size
		{ return isHorz ? size->height : size->width; }

- (NXCoord) pointX: (NXPoint const*) point
		{ return isHorz ? point->x : point->y; }
- (NXCoord) pointY: (NXPoint const*) point
		{ return isHorz ? point->y : point->x; }
- (void) point: (NXPoint*) point setX: (NXCoord) x
		{ if (isHorz) point->x = x; else point->y = x; };
- (void) point: (NXPoint*) point setY: (NXCoord) y
		{ if (isHorz) point->y = y; else point->x = y; };

- (NXCoord)rectMinX:(NXRect const*)frameRect
		{ return isHorz ? NX_X(frameRect) : NX_Y(frameRect); }
- (NXCoord)rectMaxX:(NXRect const*)frameRect
		{ return isHorz ? NX_MAXX(frameRect) : NX_MAXY(frameRect); }
- (NXCoord)rectMinY:(NXRect const*)frameRect
		{ return isHorz ? NX_Y(frameRect) : NX_X(frameRect); }
- (NXCoord)rectMaxY:(NXRect const*)frameRect
		{ return isHorz ? NX_MAXY(frameRect) : NX_MAXX(frameRect); }
- (NXCoord)rectWidth:(NXRect const*)frameRect
		{ return isHorz ? NX_WIDTH(frameRect) : NX_HEIGHT(frameRect); }
- (NXCoord)rectHeight:(NXRect const*)frameRect
		{ return isHorz ? NX_HEIGHT(frameRect) : NX_WIDTH(frameRect); }

- (void) setRect:(NXRect*)rect minX:(NXCoord)x
		{ if (isHorz) NX_X(rect) = x; else NX_Y(rect) = x; }
- (void) setRect:(NXRect*)rect minY:(NXCoord)y
		{ if (isHorz) NX_Y(rect) = y; else NX_X(rect) = y; }
- (void) setRect:(NXRect*)rect width:(NXCoord)width
		{ if (isHorz) NX_WIDTH(rect) = width; else NX_HEIGHT(rect) = width; }
- (void) setRect:(NXRect*)rect height:(NXCoord)height
		{ if (isHorz) NX_HEIGHT(rect) = height; else NX_WIDTH(rect) = height; }

- (void) changeFrameIfNeeded
	{
	MiscPixels const my_width = (MiscPixels) floor([self rectWidth:&frame]);
	MiscPixels const i_width = info->totalSize();
	if (my_width != i_width)
		{
		NXRect rect = frame;
		[window invalidateCursorRectsForView:self];
		[self setRect:&rect width:i_width];
		[self sizeTo: rect.size.width : rect.size.height ];
		[[scroll docView] adjustSize];
		}
	}

- (void) setPos:(MiscCoord_V)pos width:(MiscPixels)size
	{
	MiscCoord_P pPos = info->visualToPhysical(pos);
	if (isHorz)
		[scroll setCol:pPos size:(NXCoord)size];
	else
		[scroll setRow:pPos size:(NXCoord)size];
	[self changeFrameIfNeeded];
	[window invalidateCursorRectsForView:self];
	[scroll update];
	}

- (void) moveFrom:(MiscCoord_V)from to:(MiscCoord_V)to
	{
	if (isHorz)
		[scroll moveColFrom:from to:to];
	else
		[scroll moveRowFrom:from to:to];
	[window invalidateCursorRectsForView:self];
	[scroll update];
	}



//-----------------------------------------------------------------------------
// - initFrame:scroll:info:type:
//-----------------------------------------------------------------------------
- initFrame: (NXRect const*) frameRect
	 scroll: (MiscTableScroll*) i_scroll
	   info: (MiscTableBorder*) i_info
	   type: (MiscBorderViewType) type
	{
	NXZone* const z = [self zone];

	[super initFrame: 0];
	[self setFlipped:YES];
	[self setOpaque:YES];
	[self setClipping:NO];

	isHorz = (type == MISC_COL_BORDER_VIEW);
	scroll = i_scroll;
	info = i_info;
	theCell = [[MiscBorderCell allocFromZone:z] initTextCell:"Kilroy"];

	oldSel = new( NXZoneMalloc(z,sizeof(*oldSel)) ) MiscSparseSet;
	[self setSelectionMode: [scroll selectionMode]];

	NXRect myFrame = {{0,0},{0,0}};
	if (frameRect != 0)
		myFrame.origin = frameRect->origin;
	[self setRect: &myFrame width: i_info->totalSize()];
	[self setRect: &myFrame height: [self frameHeight]];
	[self setFrame: &myFrame];
	return self;
	}


//-----------------------------------------------------------------------------
// - free
//-----------------------------------------------------------------------------
- free
	{
	[theCell free];
	[tracker free];
	if (oldSel != 0)
		{
		oldSel->MiscSparseSet::~MiscSparseSet();
		NXZoneFree( [self zone], oldSel );
		}
	return [super free];
	}



//=============================================================================
// CONVERSIONS
//=============================================================================
//-----------------------------------------------------------------------------
// - range::fromRect:
//
//		Returns by reference the range of visual slots contained in rect.
//		rMin is inclusive, rMax is exclusive.
//-----------------------------------------------------------------------------
- (void) range: (MiscCoord_V*)rMin : (MiscCoord_V*)rMax
	fromRect: (NXRect const*)rect
	{
	*rMin = info->visualForOffset(MiscPixels(floor([self rectMinX:rect])));
	*rMax = info->visualForOffset(MiscPixels(floor([self rectMaxX:rect]))) + 1;
	}


//-----------------------------------------------------------------------------
// - rect:forPos:
//-----------------------------------------------------------------------------
- (void) rect:(NXRect*)nxrect forPos:(MiscCoord_V)pos
	{
	[self getBounds:nxrect];
	[self setRect: nxrect minX: info->getOffset(pos)];
	[self setRect: nxrect width: info->effectiveSize(pos)];
	}



//=============================================================================
// DRAWING
//=============================================================================
//-----------------------------------------------------------------------------
// - getVisibleRange::
//
//		Returns by reference the range of visual slots contained in the visible
//		rectangle.	rMin is inclusive, rMax is exclusive.
//-----------------------------------------------------------------------------
- (void) getVisibleRange: (MiscCoord_V*)rMin : (MiscCoord_V*)rMax
	{
	NXRect visRect;
	[superview getDocVisibleRect:&visRect];
	[self range:rMin:rMax fromRect:&visRect];
	}


//-----------------------------------------------------------------------------
// drewPos:
//
//		Keeps the old selection sets up to date whenever we draw a cell.  This
//		way -reflectSelection has valid data to work from.
//-----------------------------------------------------------------------------
- (void) drewPos: (MiscCoord_V)slot
	{
	BOOL isSelected = info->selectionSet().contains( slot );
	if (isSelected != oldSel->contains(slot))
		{
		if (isSelected)
			oldSel->add( slot );
		else
			oldSel->remove( slot );
		}
	}


//-----------------------------------------------------------------------------
// - drawPos:inRect:controlView:
//-----------------------------------------------------------------------------
- (void) drawPos: (MiscCoord_V) pos inRect:(NXRect const*) rect
	controlView: (View*) controlView
	{
	[theCell setStringValue: info->getTitle(pos)];
	[theCell setState: info->selectionSet().contains(pos)];
	[theCell drawSelf: rect inView: controlView];
	}


//-----------------------------------------------------------------------------
// - drawPos:updateSel:
//-----------------------------------------------------------------------------
- (void) drawPos: (MiscCoord_V) pos updateSel: (BOOL) updateSel
	{
	if (0 <= pos && pos < info->count())
		{
		NXRect rect;
		[self rect:&rect forPos:pos];
		[self drawPos:pos inRect:&rect controlView: self];
		if (updateSel)
			[self drewPos:pos];
		}
	}


//-----------------------------------------------------------------------------
// - drawPos:
//-----------------------------------------------------------------------------
- (void) drawPos: (MiscCoord_V) pos
	{
	[self drawPos:pos updateSel:YES];
	}


//-----------------------------------------------------------------------------
// - drawRect:
//-----------------------------------------------------------------------------
- (void) drawRect: (NXRect const*) rect
	{
	MiscCoord_V pos_min;
	MiscCoord_V pos_max;
	[self range:&pos_min:&pos_max fromRect:rect];
	for (MiscCoord_V pos = pos_min;	 pos < pos_max;	 pos++)
		[self drawPos:pos];
	}


//-----------------------------------------------------------------------------
// - drawSelf::
//-----------------------------------------------------------------------------
- drawSelf: (NXRect const*) rects :(int) nrects
	{
	if (nrects == 1)
		[self drawRect: rects];
	else if (nrects == 3)
		{
		[self drawRect: ++rects];
		[self drawRect: ++rects];
		}
	return self;
	}



//-----------------------------------------------------------------------------
// - drawSlot:	[public]
//-----------------------------------------------------------------------------
- (void) drawSlot: (MiscCoord_V) n
	{
	MiscCoord_V rMin, rMax;
	[self getVisibleRange:&rMin:&rMax];
	if (rMin <= n && n < rMax)
		{
		if ([self isAutodisplay] && [self canDraw])
			{
			[self lockFocus];
			[self drawPos:n];
			[self unlockFocus];
			}
		else
			{
			[self setNeedsDisplay:YES];
			}
		}
	}



//=============================================================================
// SELECTION
//=============================================================================
//-----------------------------------------------------------------------------
// - setSelectionMode:
//-----------------------------------------------------------------------------
- (void) setSelectionMode: (MiscSelectionMode) mode
	{
	NXZone* const z = [self zone];
	if (tracker)
		[tracker free];
	switch (mode)
		{
		case MISC_LIST_MODE:
			tracker = [MiscListTracker allocFromZone:z];
			break;
		case MISC_RADIO_MODE:
			tracker = [MiscRadioTracker allocFromZone:z];
			break;
		case MISC_HIGHLIGHT_MODE:
			tracker = [MiscHighlightTracker allocFromZone:z];
			break;
		}
	[tracker initBorder: info];
	}


//-----------------------------------------------------------------------------
// - reflectSelection
//-----------------------------------------------------------------------------
- (void) reflectSelection
	{
	if ([self canDraw])
		{
		MiscSparseSet const& newSel = info->selectionSet();

		MiscCoord_V rMin, rMax;
		[self getVisibleRange:&rMin:&rMax];
	
		[window disableFlushWindow];
		int locked = 0;
		for (MiscCoord_V i = rMin;	i < rMax;  i++)
			{
			if (oldSel->contains(i) != newSel.contains(i))
				{
				if (!locked)
					{
					locked = 1;
					[self lockFocus];
					}
				[self drawPos:i updateSel:NO];
				}
			}
		if (locked)
			[self unlockFocus];
	
		[[window reenableFlushWindow] flushWindow];
		*oldSel = newSel;
		}
	}


//-----------------------------------------------------------------------------
// - selectPos:
//-----------------------------------------------------------------------------
- (void) selectPos: (MiscCoord_V) pos
	{
	MiscSparseSet& s = info->selectionSet();
	s.empty();
	s.add( pos );
	[scroll selectionChanged];
	}



//=============================================================================
// CURSOR MANAGEMENT
//=============================================================================
//-----------------------------------------------------------------------------
// - cursorRect:forPos:
//-----------------------------------------------------------------------------
- (BOOL) cursorRect:(NXRect*)nxrect forPos:(MiscCoord_V)pos;
	{
	MiscPixels const pos_width = info->effectiveSize(pos);
	if (pos_width > 0)
		{
		MiscPixels const pos_offset = info->getOffset(pos);
		MiscPixels const min_x = pos_offset + pos_width - MISC_RESIZE_EPSILON;
		*nxrect = bounds;
		[self setRect:nxrect minX:min_x];
		[self setRect:nxrect width:MISC_RESIZE_EPSILON];
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// - resetCursorRects
//-----------------------------------------------------------------------------
- resetCursorRects
	{
	if (info->isSizeable())
		{
		NXCursor* const cursor = [self cursor];
		MiscCoord_V min_pos, max_pos;
		[self getVisibleRange:&min_pos:&max_pos];
		int const count = info->count();
		if (0 <= min_pos && min_pos < count)
			{
			NXRect rect;
			int const lim = (max_pos <= count) ? max_pos : count;
			for (MiscCoord_V pos = min_pos;	 pos < lim;	 pos++)
				if (info->isSizeable(pos) && [self cursorRect:&rect forPos:pos])
					[self addCursorRect:&rect cursor:cursor];
			}
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - refresh
//-----------------------------------------------------------------------------
- (void) refresh
	{
	[self changeFrameIfNeeded];
	if ([self canDraw])
		{
		[window disableFlushWindow];
		NXRect rect;
		[superview getDocVisibleRect:&rect];
		[self lockFocus];
		[self drawSelf:&rect:1];
		[self unlockFocus];
		[[window reenableFlushWindow] flushWindow];
		}
	}



//=============================================================================
// MOUSE-TRACKING
//=============================================================================
//-----------------------------------------------------------------------------
// - startTracking
//-----------------------------------------------------------------------------
- (void) startTracking
	{
	[self clearOtherBorder];
	if ([window firstResponder] == [scroll docView])
		[window makeFirstResponder: window];
	}


//-----------------------------------------------------------------------------
// - endTracking:
//-----------------------------------------------------------------------------
- (void) endTracking: (MiscCoord_V)pos
	{
	if (pos < 0)
		pos = 0;
	else if (pos >= info->count())
		pos = info->count() - 1;
	[scroll border:[self borderType] setCursor:info->visualToPhysical(pos)];
	[scroll selectText: self];
	}


//-----------------------------------------------------------------------------
// - posForMousePt:
//-----------------------------------------------------------------------------
- (MiscCoord_V) posForMousePt: (NXPoint const*) p
	{
	MiscPixels pix = MiscPixels( floor([self pointX: p]) );
	return (pix < [self rectMaxX: &bounds] ?
				info->visualForOffset( pix ) : info->count());
	}



//=============================================================================
// RESIZING
//=============================================================================
//-----------------------------------------------------------------------------
// - resizeEvent:x:deltaX:minX:maxX:
//-----------------------------------------------------------------------------
- (int) resizeEvent:(NXEvent const*)p
		x:(MiscPixels)x
		deltaX:(MiscPixels)deltaX
		minX:(MiscPixels)minX
		maxX:(MiscPixels)maxX
	{
	NXRect docFrame;	[scroll getDocClipFrame: &docFrame];
	NXRect clipFrame;	[superview getFrame: &clipFrame];
	NXCoord const minDrawX = [self rectMinX: &clipFrame]; // scroll coords
	NXCoord const maxDrawX = [self rectMaxX: &clipFrame]; // scroll coords

	x += deltaX;

	NXCoord draw_x;				// scroll coords
	NXRect line;
	[self setRect:&line height:[self rectHeight:&clipFrame] +
								[self rectHeight:&docFrame] - 1 ];
	[self setRect:&line width: 2 ];
	[self setRect:&line minX: x - 1 ];
	[self setRect:&line minY: 0 ];
	[scroll convertPoint:&(line.origin) fromView:self];
	draw_x = [self rectMinX:&line];

	BOOL did_scroll = NO;
	BOOL in_bounds = (minDrawX <= draw_x && draw_x <= maxDrawX);

	[scroll lockFocus];
	PSsetinstance(YES);
	PSsetgray(NX_BLACK);

	if (in_bounds)
		PSrectfill( line.origin.x, line.origin.y,
						line.size.width, line.size.height );

	NXTrackingTimer* timer = 0;
	startTimer( timer );
	NXEvent lastEvent = *p;

	while (1)
		{
		p = [NXApp getNextEvent:NX_MOUSEUPMASK |
								NX_MOUSEDRAGGEDMASK |
								NX_TIMERMASK ];
		if (p == 0 || p->type == NX_MOUSEUP)
			break;
		if (p->type == NX_TIMER)
			{
			NXPoint mousePt = lastEvent.location;
			[scroll convertPoint:&mousePt fromView:0];
			if (mousePt.x < minDrawX || maxDrawX < mousePt.x)
				{
				PSsetinstance(NO);
				[self autoscroll: &lastEvent];
				PSsetinstance(YES);
				did_scroll = YES;
				}
			}
		else
			lastEvent = *p;

		NXPoint new_loc = lastEvent.location;
		[self convertPoint:&new_loc fromView:0];
		MiscPixels mouseX = (MiscPixels) floor([self pointX:&new_loc]);
		MiscPixels new_x = (mouseX + deltaX);
		if (new_x < minX)
			new_x = minX;
		else if (new_x >= maxX)
			new_x = maxX - 1;

		BOOL const did_move = (new_x != x);
		if (did_move || did_scroll)
			{
			if (in_bounds && !did_scroll)
				PShideinstance( line.origin.x, line.origin.y,
								line.size.width, line.size.height );
			if (did_move)
				{
				x = new_x;
				[self setRect:&line minX: x - 1 ];
				[self setRect:&line minY:0];
				[scroll convertPoint:&(line.origin) fromView:self];
				draw_x = [self rectMinX:&line];
				in_bounds = (minDrawX <= draw_x && draw_x <= maxDrawX);
				}

			if (in_bounds)
				PSrectfill( line.origin.x, line.origin.y,
								line.size.width, line.size.height );
			did_scroll = NO;
			}
		}

	stopTimer( timer );

	if (in_bounds)
		PShideinstance( line.origin.x, line.origin.y,
						line.size.width, line.size.height );

	PSsetinstance(NO);
	[scroll unlockFocus];

	x -= deltaX;
	return x;
	}


//-----------------------------------------------------------------------------
// - resizeEvent:inPos:atX:deltaX:finalWidth:
//-----------------------------------------------------------------------------
- (BOOL) resizeEvent: (NXEvent const*) p
		 inPos: (MiscCoord_V) pos
		 atX: (MiscPixels) x_origin
		 deltaX: (MiscPixels) delta_x
		 finalWidth: (MiscPixels*) finalWidth
	{
	MiscPixels const org_x = info->getOffset(pos);
	MiscPixels const min_x = org_x + info->effectiveMinSize(pos);
	MiscPixels const max_x = org_x + info->effectiveMaxSize(pos) + 1;
	MiscPixels const curr_x = [self resizeEvent:p x:x_origin deltaX:delta_x
								minX:min_x maxX:max_x];
	MiscPixels final_delta = curr_x - x_origin;
	if (final_delta != 0)
		{
		*finalWidth = info->effectiveSize(pos) + final_delta;
		return YES;
		}

	return NO;
	}


//-----------------------------------------------------------------------------
// - inHotZone:forPos:atX:deltaX:
//-----------------------------------------------------------------------------
- (BOOL) inHotZone: (NXPoint const*) pt
		forPos: (MiscCoord_V*) pos
		atX: (MiscPixels*) pos_x
		deltaX: (MiscPixels*) delta_x
	{
	if (info->isSizeable())
		{
		MiscCoord_V const plim = info->count();
		MiscPixels x = (MiscPixels) floor([self pointX:pt]);
		MiscCoord_V p = info->visualForOffset(x);
		if (0 <= p && p < plim)
			{
			MiscPixels max_x = info->getOffset(p) + info->effectiveSize(p);
			MiscPixels delta = max_x - x;
		
			if (0 <= delta && delta <= MISC_RESIZE_EPSILON)
				{
				do { p++; } while (p < plim && info->effectiveSize(p) <= 0);
				p--;
				if (0 <= p && p < plim && info->isSizeable(p))
					{
					*pos = p;
					*pos_x = x;
					*delta_x = delta;
					return YES;
					}
				}
			}
		}

	return NO;
	}


//-----------------------------------------------------------------------------
// - adjustSize
//-----------------------------------------------------------------------------
- (void) adjustSize
	{
	[self changeFrameIfNeeded];
	}



//=============================================================================
// DRAGGING
//=============================================================================
//-----------------------------------------------------------------------------
// - drawDocCells:at:controlView
//-----------------------------------------------------------------------------
- (void) drawDocCells: (MiscCoord_V) pos at: (NXPoint const*) pt
	controlView: (View*) controlView
	{
	NXRect rDoc;
	[[scroll docView] getVisibleRect: &rDoc];
	MiscTableBorder* b = [self otherBorder];
	MiscCoord_V sMin = b->visualForOffset(MiscPixels([self rectMinY: &rDoc]));
	MiscCoord_V sMax = b->visualForOffset(MiscPixels([self rectMaxY: &rDoc]));
	if (sMin >= 0 && sMax >= 0)
		{
		MiscCoord_P p_pos = info->visualToPhysical( pos );
		NXCoord delta = [self rectMinY: &rDoc] - b->getOffset( sMin );
	
		NXRect r = {{0,0},{0,0}};
		[self setRect: &r minY: [self pointY: pt] - delta];
		[self setRect: &r width: info->effectiveSize( pos )];
	
		for (int i = sMin; i <= sMax; i++)
			{
			id cell = [self cellAt:b->visualToPhysical(i):p_pos];
			[self setRect: &r height: b->effectiveSize(i)];
			[cell drawSelf: &r inView: controlView];
			[self setRect:&r minY:[self rectMinY:&r] + [self rectHeight:&r]];
			}
		}
	}


//-----------------------------------------------------------------------------
// - setDragCache:forPos:
//
//		NOTE *1*: We create a special window just for the sake of getting the
//				text on the drag-cache to display properly. 1) By creating a
//				window, the cells have an object to which they can send their
//				getFieldEditor:for: message.  2) By providing a view which is
//				at least as large as the drag-cache we avoid clipping problems.
//				If it wasn't large enough the text would get clipped at the
//				wrong places.
//		NOTE *2*: The weird combination of non-deferred window plus
//				-reenableDisplay was necessary to get the text to actually
//				display.  Without both of them text wouldn't draw at all.
//				Let's have a look at that AppKit source!
//-----------------------------------------------------------------------------
- (void) setDragCache: (NXImage*) cache forPos: (MiscCoord_V) pos
	{
	Window* w = [[Window allocFromZone: [self zone]]			// NOTE *1*
				initContent: 0 style: NX_PLAINSTYLE
				backing: NX_NONRETAINED buttonMask: 0 defer: NO];
	[w reenableDisplay];										// NOTE *2*
	[[w contentView] setFlipped: YES];

	NXRect rDoc;		[[scroll docView] getVisibleRect: &rDoc];
	NXRect rClip;		[superview getFrame: &rClip];
	[scroll convertRect: &rDoc fromView: [scroll docView]];

	NXRect r = {{0,0},{0,0}};
	[self setRect: &r width: info->effectiveSize( pos )];
	[self setRect: &r height:
				([self rectHeight: &rClip] + [self rectHeight: &rDoc])];

	NXRect rBorder = r;
	[self setRect: &rBorder height: [self rectHeight: &rClip]];

	NXRect rContent = r;
	[self setRect: &rContent minY: [self rectHeight: &rClip]];

	[w sizeWindow: NX_WIDTH(&r) : NX_HEIGHT(&r)];
	[cache setFlipped: YES];
	[cache setSize: &r.size];
	[cache lockFocus];
	[self drawDocCells: pos at: &rContent.origin controlView: [w contentView]];
	[self drawPos: pos inRect: &rBorder controlView: [w contentView]];
	[cache unlockFocus];

	[w free];
	}


//-----------------------------------------------------------------------------
// - setVisibleCache:min:max:
//-----------------------------------------------------------------------------
- (void) setVisibleCache:(NXImage*)cache min:(NXCoord*)pMin max:(NXCoord*)pMax
	{
	NXRect rDoc;		[[scroll docView] getVisibleRect: &rDoc];
	NXRect rVis;		[self getVisibleRect: &rVis];
	*pMin = [self rectMinX: &rVis];
	*pMax = [self rectMaxX: &rVis];
	[scroll convertRect: &rDoc fromView: [scroll docView]];
	[scroll convertRect: &rVis fromView: self];

	NXRect r;
	[self setRect: &r minX: [self rectMinX: &rVis]];
	[self setRect: &r minY: [self rectMinY: &rVis]];
	[self setRect: &r width: [self rectWidth: &rVis]];
	[self setRect: &r height:
				([self rectHeight: &rVis] + [self rectHeight: &rDoc])];

	[scroll convertRect: &r toView: 0];
	[cache setSize: &r.size];
	[cache lockFocus];
	PScomposite( NX_X(&r), NX_Y(&r), NX_WIDTH(&r), NX_HEIGHT(&r),
				[window gState], 0.0, 0.0, NX_COPY );
	[cache unlockFocus];
	}


//-----------------------------------------------------------------------------
// - setWells::forPos:
//-----------------------------------------------------------------------------
- (void) setWells: (MiscTableWell**) w1 : (MiscTableWell**) w2
	forPos: (MiscCoord_V) pos
	{
	NXZone* const z = [self zone];
	MiscTableView* doc = [scroll docView];
	NXRect rDoc;		[doc getVisibleRect: &rDoc];
	NXRect rClip;		[superview getFrame: &rClip];

	NXRect r = {{0,0},{0,0}};
	[self setRect: &r minX: info->getOffset( pos )];
	[self setRect: &r width: info->effectiveSize( pos )];
	[self setRect: &r height: [self rectHeight: &rClip]];

	*w1 = [[MiscTableWell allocFromZone:z] initFrame: &r];
	[self addSubview: *w1];

	[self setRect: &r minY: [self rectMinY: &rDoc]];
	[self setRect: &r height: [self rectHeight: &rDoc]];
	*w2 = [[MiscTableWell allocFromZone:z] initFrame: &r];
	[doc addSubview: *w2];

	[*w1 display];
	[*w2 display];
	}


//-----------------------------------------------------------------------------
// - clearWells::
//-----------------------------------------------------------------------------
- (void) clearWells: (MiscTableWell**) w1 : (MiscTableWell**) w2
	{
	[*w1 removeFromSuperview];
	[*w2 removeFromSuperview];
	[*w1 free];
	[*w2 free];
	*w1 = 0;
	*w2 = 0;
	// NOTE: Does not need display here.  Everything will get displayed later.
	}


//-----------------------------------------------------------------------------
// - offsetFromEvent:
//-----------------------------------------------------------------------------
- (NXCoord) offsetFromEvent: (NXEvent const*) ev
	{
	NXPoint mLoc = ev->location;
	[self convertPoint: &mLoc fromView: 0];
	return floor( [self pointX: &mLoc] );
	}


//-----------------------------------------------------------------------------
// - calcDrop::
//-----------------------------------------------------------------------------
- (MiscCoord_V) calcDrop: (MiscCoord_V) fromPos
						: (NXPoint const*) mouseDownPt
						: (NXPoint const*) mouseUpPt
	{
	MiscCoord_V toPos = fromPos;

	MiscPixels const start_pos = (MiscPixels) floor([self pointX:mouseDownPt]);
	MiscPixels const end_pos   = (MiscPixels) floor([self pointX:mouseUpPt]);
	MiscPixels const delta_pos = (end_pos - start_pos);

	MiscPixels const SLOP = 4;
	if (delta_pos < -SLOP || SLOP < delta_pos)
		{
		MiscPixels const start_ofs = info->getOffset( fromPos );
		MiscPixels drop_pos = start_ofs + delta_pos;
		if (delta_pos < 0)
			drop_pos += SLOP;
		else
			drop_pos += info->effectiveSize(fromPos) - SLOP;
		toPos = info->visualForOffset( drop_pos );
		if (toPos < 0)
			toPos = 0;
		}

	return toPos;
	}


//-----------------------------------------------------------------------------
// - dragEvent:inPos:
//-----------------------------------------------------------------------------
- (MiscCoord_V) dragEvent: (NXEvent*) event inPos: (MiscCoord_V) pos
	{
	int const WANTED = (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK | NX_TIMERMASK);
	NXPoint mouseDownPt = event->location;

	[self convertPoint:&mouseDownPt fromView:0];
	NXPoint mouseUpPt = mouseDownPt;

	[window disableFlushWindow];

	MiscTableWell *w1, *w2;
	[self setWells: &w1 : &w2 forPos: pos];

	NXZone* const z = [self zone];
	NXCoord pMin,pMax;
	NXImage* visCache = [[NXImage allocFromZone:z] init];
	NXImage* dragCache = [[NXImage allocFromZone:z] init];
	[self setVisibleCache: visCache min: &pMin max: &pMax];
	[self setDragCache: dragCache forPos: pos];

	NXSize size;
	[dragCache getSize: &size];
	NXCoord pLoc = info->getOffset( pos );
	NXCoord delta = [self offsetFromEvent: event] - pLoc;

	NXTrackingTimer* timer = 0;
	NXEvent lastEvent = *event;
	[scroll lockFocus];
	while (1)
		{
		NXPoint pt;
		NXRect rDrag = {{0,0},{0,0}};
		NXCoord const w = [self sizeWidth: &size];
		NXCoord const dw =
			dmin( dmin( dmin(w, pMax - pMin), pMax - pLoc), pLoc + w - pMin );
		BOOL const shouldDraw = (dw > 0.0);
		if (shouldDraw)
			{
			if (isHorz)
				{
				pt.x = dmax( pLoc, pMin );
				pt.y = size.height;
				if (pLoc < pMin)
					rDrag.origin.x = (pMin - pLoc);
				}
			else
				{
				pt.x = 0.0;
				pt.y = dmax( pLoc, pMin ) + dw;
				if (pLoc + w >= pMax)
					rDrag.origin.y = pLoc + w - pMax;
				}
			[scroll convertPoint: &pt fromView: self];
			[self setRect: &rDrag width: dw];
			[self setRect: &rDrag height: [self sizeHeight: &size]];
			[dragCache composite: NX_COPY fromRect: &rDrag toPoint: &pt];
			}
		[[window reenableFlushWindow] flushWindow];

		event = [NXApp getNextEvent: WANTED];

		[window disableFlushWindow];
		if (shouldDraw)
			{
			NXRect rVis = {{0,0},{0,0}};
			NXSize s; [visCache getSize: &s];
			NXCoord xTarg;
			if (isHorz)
				{
				xTarg = (pLoc < pMin ? 0.0 : pLoc - pMin);
				}
			else
				{
				if (pLoc < pMin)
					xTarg = s.height - dw;
				else if (pLoc < pMax)
					xTarg = pMax - pLoc - dw;
				else
					xTarg = 0.0;
				}
			[self setRect: &rVis minX: xTarg];
			[self setRect: &rVis width: [self rectWidth: &rDrag]];
			[self setRect: &rVis height: [self rectHeight: &rDrag]];
			[visCache composite: NX_COPY fromRect: &rVis toPoint: &pt];
			}

		if (event == 0)
			break;
		if (event->type == NX_MOUSEUP)
			{
			mouseUpPt = event->location;
			[self convertPoint:&mouseUpPt fromView:0];
			break;
			}
		if (event->type != NX_TIMER)
			lastEvent = *event;

		NXCoord mLoc = [self offsetFromEvent: &lastEvent];
		if ((mLoc < pMin && pMin > 0.0) ||
			(mLoc > pMax && pMax < [self rectMaxX: &bounds]))
			{
			[self autoscroll: &lastEvent];
			[self setVisibleCache: visCache min: &pMin max: &pMax];
			mLoc = [self offsetFromEvent: &lastEvent];
			startTimer( timer );
			}
		else
			{
			stopTimer( timer );
			}

		pLoc = mLoc - delta;
		if (pLoc < pMin - [self sizeWidth: &size])
			pLoc = pMin - [self sizeWidth: &size];
		else if (pLoc > pMax)
			pLoc = pMax;
		}

	stopTimer( timer );
	[scroll unlockFocus];
	[self clearWells: &w1 : &w2];
	[visCache free];
	[dragCache free];

	MiscCoord_V const toPos = [self calcDrop:pos :&mouseDownPt :&mouseUpPt];

	if (toPos != pos)
		{
		BOOL const was_auto = [scroll isAutodisplay];
		[scroll setAutodisplay:NO];
		[self moveFrom: pos to: toPos];
		[self selectPos: toPos];
		[scroll border:[self borderType] slotDraggedFrom:pos to:toPos];
		[scroll setAutodisplay:was_auto];		// Will display if needed.
		}
	else
		{
		// Need to redisplay the slot that the wells were covering.
		MiscBorderType const b = [self borderType];
		int const phys_pos = [scroll border:b slotAtPosition:toPos];
		[scroll border:b drawSlotTitle:phys_pos];
		[scroll border:b drawSlot:phys_pos];
		}

	[[window reenableFlushWindow] flushWindow];
	return toPos;
	}


//-----------------------------------------------------------------------------
// - awaitDragEvent:inPos:
//-----------------------------------------------------------------------------
- (MiscCoord_V) awaitDragEvent: (NXEvent const*) event inPos: (MiscCoord_V) pos
	{
	MiscCoord_V toPos = pos;
	int const WANTED = (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK);
	float const FOREVER = FLT_MAX;
	float const SLOP = 2.0;
	NXEvent mouseDown = *event;
	for (;;)
		{
		NXEvent peek;
		NXEvent* p = [NXApp peekNextEvent: WANTED into: &peek waitFor: FOREVER
							threshold: NX_MODALRESPTHRESHOLD];
		if (p == 0 || p->type == NX_MOUSEUP)
			break;
		else if (p->location.x > mouseDown.location.x + SLOP ||
				 p->location.y > mouseDown.location.y + SLOP ||
				 p->location.x < mouseDown.location.x - SLOP ||
				 p->location.y < mouseDown.location.y - SLOP)
			{
			toPos = [self dragEvent: &mouseDown inPos: pos];
			break;
			}
		else
			[NXApp getNextEvent: NX_MOUSEDRAGGEDMASK];
		}
	return toPos;
	}
	


//=============================================================================
// SELECTION
//=============================================================================
//-----------------------------------------------------------------------------
// - selectionEvent:fromPos:
//-----------------------------------------------------------------------------
- (void) selectionEvent:(NXEvent const*)p fromPos:(MiscCoord_V)pos
	{
	BOOL doubleClicked = (p->data.mouse.click > 1);

	[tracker mouseDown: p atPos: pos];
	[scroll reflectSelection];

	NXTrackingTimer* timer = 0;
	startTimer( timer );
	NXEvent lastEvent = *p;

	while (1)
		{
		p = [NXApp getNextEvent:NX_MOUSEUPMASK |
								NX_MOUSEDRAGGEDMASK |
								NX_TIMERMASK ];

		if (p == 0 || p->type == NX_MOUSEUP)
			break;
		else if (p->type == NX_TIMER)
			[self autoscroll: &lastEvent];
		else
			lastEvent = *p;

		NXPoint new_loc = lastEvent.location;
		[self convertPoint:&new_loc fromView:0];
		MiscCoord_V new_pos = [self posForMousePt: &new_loc];
		if (new_pos != pos)
			{
			pos = new_pos;
			[tracker mouseDragged: p atPos: pos];
			[scroll reflectSelection];
			}
		}

	stopTimer( timer );

	[tracker mouseUp: p atPos: pos];
	[scroll reflectSelection];
	[self endTracking: pos];

	if (doubleClicked)
		[scroll sendDoubleActionIfEnabled];
	else
		[scroll sendActionIfEnabled];
	}



//=============================================================================
// MOUSE-EVENTS
//=============================================================================
//-----------------------------------------------------------------------------
// - acceptsFirstMouse
//-----------------------------------------------------------------------------
- (BOOL) acceptsFirstMouse
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - mouseDown:
//-----------------------------------------------------------------------------
- mouseDown:(NXEvent*)p
	{
	NXPoint evpt = p->location;
	[self convertPoint: &evpt fromView: 0];
	MiscCoord_V pos = [self posForMousePt: &evpt];
	int old_mask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	MiscPixels x, delta_x;

	if ([self inHotZone:&evpt forPos:&pos atX:&x deltaX:&delta_x])
		{
		[self startTracking];
		[self selectPos:pos];
		MiscPixels finalWidth;
		BOOL doit;
		doit = [self resizeEvent:p inPos:pos atX:x deltaX:delta_x
				finalWidth:&finalWidth];
		if (doit)
			{
			[window disableFlushWindow];
			[self setPos:pos width:finalWidth];
			[scroll border:[self borderType] slotResized:pos];
			[[window reenableFlushWindow] flushWindow];
			}
		[self endTracking:pos];
		[scroll sendActionIfEnabled];
		}
	else if (info->isDraggable() &&
		(info->isModifierDrag() == ((p->flags & NX_COMMANDMASK) != 0)))
		{
		[self startTracking];
		[self selectPos:pos];
		[window flushWindow];
		DPSFlush();
		MiscCoord_V toPos = [self awaitDragEvent:p inPos:pos];
		[self endTracking:toPos];
		[scroll sendActionIfEnabled];
		}
	else
		{
		assert( tracker != 0 );
		[self startTracking];
		[self selectionEvent: p fromPos: pos];
		}

	[window setEventMask:old_mask];
	return self;
	}

@end
